#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui_(new Ui::MainWindow)
    , serialPort_(new QSerialPort(this))
    , timeoutTimer_(new QTimer(this))  // 初始化定时器
{
    ui_->setupUi(this);
    initMainWindow();                  // 初始化界面
    initSignalSlot();                  // 初始化信号槽
    autoFillSerialPorts();             // 自动检测并填充串口
}

MainWindow::~MainWindow()
{
    if(serialPort_->isOpen())
        serialPort_->close();
    delete ui_;
}

void MainWindow::initMainWindow()
{
    this->setWindowTitle("Modbus主站");
    this->setWindowIcon(QIcon(":/resource/image/modbus.png"));
    ui_->logo->setIcon(QIcon(":/resource/image/noconnect.png"));
    ui_->logo->setStyleSheet("QPushButton { background-color: transparent; }");

    // 按钮样式设置
    QString buttonStyle = "QPushButton { text-align: center; padding: 5px 15px; qproperty-iconSize: 24px 24px; }";

    ui_->exportButton->setIcon(QIcon(":/resource/image/export.png"));
    ui_->exportButton->setIconSize(QSize(24, 24));
    ui_->exportButton->setStyleSheet(buttonStyle);

    ui_->deleteButton->setIcon(QIcon(":/resource/image/delete.png"));
    ui_->deleteButton->setIconSize(QSize(24, 24));
    ui_->deleteButton->setStyleSheet(buttonStyle);

    ui_->readButton->setIcon(QIcon(":/resource/image/read.png"));
    ui_->readButton->setIconSize(QSize(24, 24));
    ui_->readButton->setStyleSheet(buttonStyle);

    ui_->writeButton->setIcon(QIcon(":/resource/image/write.png"));
    ui_->writeButton->setIconSize(QSize(24, 24));
    ui_->writeButton->setStyleSheet(buttonStyle);
}

void MainWindow::initSignalSlot()
{
    // 关联信号与槽
    connect(ui_->connectButton, &QPushButton::clicked, this, &MainWindow::onConnectButtonClicked);
    connect(ui_->readButton, &QPushButton::clicked, this, &MainWindow::onReadButtonClicked);
    connect(ui_->writeButton, &QPushButton::clicked, this, &MainWindow::onWriteButtonClicked);
    connect(ui_->deleteButton, &QPushButton::clicked, this, &MainWindow::onDeleteButtonClicked);
    connect(ui_->exportButton, &QPushButton::clicked, this, &MainWindow::onExportButtonClicked);
    connect(serialPort_, &QSerialPort::readyRead, this, &MainWindow::onSerialDataReady);
    connect(serialPort_, &QSerialPort::errorOccurred, this, &MainWindow::handleSerialError);
    connect(timeoutTimer_, &QTimer::timeout, this, &MainWindow::handleTimeout);
}

void MainWindow::autoFillSerialPorts()
{
    ui_->comboBox_serial->clear();
    const QList<QSerialPortInfo> portInfos = QSerialPortInfo::availablePorts();
    if (portInfos.isEmpty()) {
        ui_->comboBox_serial->addItem("无可用串口");
        return;
    }

    QMap<int, QString> portMap;
    for (const QSerialPortInfo &info : portInfos) {
        QString portName = info.portName();
        int portNumber = portName.mid(3).toInt();
        portMap[portNumber] = portName;
    }

    for (const QString &portName : portMap)
        ui_->comboBox_serial->addItem(portName);

    if (ui_->comboBox_serial->count() > 0)
        ui_->comboBox_serial->setCurrentIndex(0);
    else
        ui_->comboBox_serial->addItem("无可用串口（无权限或被占用）");
}

void MainWindow::setRequestButtonsEnabled(bool enabled)
{
    ui_->readButton->setEnabled(enabled);
    ui_->writeButton->setEnabled(enabled);
}

void MainWindow::onConnectButtonClicked()
{
    if (isConnected_) {
        serialPort_->close();
        isConnected_ = false;
        ui_->connectStatus->setText("未连接");
        ui_->logo->setIcon(QIcon(":/resource/image/noconnect.png"));
        ui_->connectButton->setText("连接");
        QMessageBox::information(this, "提示", "已断开连接");

        QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
        ui_->textEdit_log->append(QString("[%1] 串口已断开\n").arg(timestamp));
        ui_->textEdit_log->moveCursor(QTextCursor::End);
        return;
    }

    QString portText = ui_->comboBox_serial->currentText();
    if (portText.isEmpty() || portText == "无可用串口") {
        QMessageBox::information(this, "提示", "无有效串口可选");
        return;
    }
    serialPort_->setPortName(portText);

    int baudRate = ui_->comboBox_baud->currentText().toInt();
    serialPort_->setBaudRate(baudRate);

    int databits = ui_->comboBox_data->currentText().toInt();
    switch (databits) {
    case 5: serialPort_->setDataBits(QSerialPort::Data5); break;
    case 6: serialPort_->setDataBits(QSerialPort::Data6); break;
    case 7: serialPort_->setDataBits(QSerialPort::Data7); break;
    case 8: serialPort_->setDataBits(QSerialPort::Data8); break;
    default:
        QMessageBox::information(this, "提示", "无效的数据位!");
        return;
    }

    QString parityText = ui_->comboBox_parity->currentText();
    if (parityText == "无校验")
        serialPort_->setParity(QSerialPort::NoParity);
    else if (parityText == "奇校验")
        serialPort_->setParity(QSerialPort::OddParity);
    else if (parityText == "偶校验")
        serialPort_->setParity(QSerialPort::EvenParity);
    else {
        QMessageBox::information(this, "提示", "无效的校验位!");
        return;
    }

    QString stopBitsText = ui_->comboBox_stop->currentText();
    if (stopBitsText == "1")
        serialPort_->setStopBits(QSerialPort::OneStop);
    else if (stopBitsText == "2")
        serialPort_->setStopBits(QSerialPort::TwoStop);
    else {
        QMessageBox::information(this, "提示", "无效的停止位!");
        return;
    }

    if (serialPort_->open(QIODevice::ReadWrite)) {
        isConnected_ = true;
        ui_->connectStatus->setText("已连接");
        ui_->logo->setIcon(QIcon(":/resource/image/connect.png"));
        ui_->connectButton->setText("断开");
        QMessageBox::information(this, "提示", "已连接到 " + portText);

        QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
        ui_->textEdit_log->append(QString("[%1] 成功连接串口: %2").arg(timestamp, portText));
        ui_->textEdit_log->moveCursor(QTextCursor::End);
    } else {
        QString error = "连接失败: " + serialPort_->errorString();
        QMessageBox::warning(this, "错误", error);
        qDebug() << error;
    }
}

void MainWindow::onReadButtonClicked()
{
    if (!serialPort_->isOpen()) {
        QMessageBox::information(this, "提示", "读取失败: 未连接设备");
        return;
    }

    QByteArray request = buildModbusRequest();
    if (request.isEmpty())
        return;

    serialPort_->write(request);
    lastRequestFrame_ = request;
    currentRetryCount_ = 0;
    waitingResponse_ = true;
    startTimeoutMonitoring();
    setRequestButtonsEnabled(false);

    QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
    QString logEntry = QString("[%1] 读请求帧: %2").arg(timestamp, request.toHex(' ').toUpper());
    ui_->textEdit_log->append(logEntry);
    ui_->textEdit_log->moveCursor(QTextCursor::End);

    QMessageBox::information(this, "提示", "数据读取成功");
}

QByteArray MainWindow::calculateCrc(const QByteArray &data)
{
    unsigned int crc = 0xFFFF;
    for (int i = 0; i < data.size(); ++i) {
        crc ^= static_cast<unsigned char>(data[i]);
        for (int j = 0; j < 8; ++j) {
            if (crc & 0x0001)
                crc = (crc >> 1) ^ 0xA001;
            else
                crc >>= 1;
        }
    }
    QByteArray result;
    result.append(static_cast<char>(crc & 0xFF));
    result.append(static_cast<char>(crc >> 8));
    return result;
}

QByteArray MainWindow::buildModbusRequest()
{
    int slaveAddr = ui_->spinBox_slaveAddr->value();
    QString slaveStrAddr = QString("%1").arg(slaveAddr, 2, 16, QChar('0')).toUpper();

    int startAddr = ui_->spinBox_startAddr->value();
    QString addrHex = QString("%1").arg(startAddr, 4, 16, QChar('0')).toUpper();

    quint16 count = ui_->spinBox_count->value();
    expectedCoilCount_ = count;
    QString countHex = QString("%1").arg(count, 4, 16, QChar('0')).toUpper();

    QString funcText = ui_->comboBox_function->currentText();
    QString funcCode;
    if (funcText == "(0x01)读线圈")
        funcCode = "01";
    else if (funcText == "(0x03)读寄存器")
        funcCode = "03";
    else if (funcText == "(0x0F)写线圈")
        funcCode = "0F";
    else if (funcText == "(0x10)写寄存器")
        funcCode = "10";
    else {
        QMessageBox::information(this, "提示", "请选择写入功能码");
        return QByteArray();
    }

    QString dataText = ui_->textEdit_write->toPlainText().trimmed();
    if ((funcCode == "0F" || funcCode == "10") && dataText.isEmpty()) {
        QMessageBox::information(this, "提示", "请输入写入数据");
        return QByteArray();
    }

    QString message = slaveStrAddr + funcCode + addrHex + countHex;
    if (funcCode == "0F") {
        if (dataText.length() != count) {
            QMessageBox::information(this, "提示", "二进制数据长度不匹配");
            return QByteArray();
        }
        int byteCount = (count + 7) / 8;
        QString byteCountHex = QString("%1").arg(byteCount, 2, 16, QChar('0')).toUpper();
        QString dataHex;
        for (int i = 0; i < byteCount; ++i) {
            quint8 byte = 0;
            for (int bit = 0; bit < 8; ++bit) {
                int dataIndex = i * 8 + bit;
                if (dataIndex < dataText.length() && dataText[dataIndex] == '1')
                    byte |= (1 << bit);
            }
            dataHex += QString("%1").arg(byte, 2, 16, QChar('0')).toUpper();
        }
        message += (byteCountHex + dataHex);
    } else if (funcCode == "10") {
        QStringList dataList = dataText.split(',');
        if (dataList.size() != count) {
            QMessageBox::information(this, "提示", "数据数量不匹配");
            return QByteArray();
        }
        int byteCount = count * 2;
        QString byteCountHex = QString("%1").arg(byteCount, 2, 16, QChar('0')).toUpper();
        QString dataHex;
        for (const QString &val : dataList) {
            bool ok;
            int intVal = val.toInt(&ok);
            if (!ok) {
                QMessageBox::information(this, "提示", "无效的数据格式");
                return QByteArray();
            }
            dataHex += QString("%1").arg(intVal, 4, 16, QChar('0')).toUpper();
        }
        message += (byteCountHex + dataHex);
    }

    QByteArray request = QByteArray::fromHex(message.toUtf8());
    QByteArray crcData = calculateCrc(request);
    request.append(crcData);
    return request;
}

void MainWindow::onWriteButtonClicked()
{
    if (!serialPort_->isOpen()) {
        QMessageBox::warning(this, "错误", "写入失败: 未连接设备");
        return;
    }

    QByteArray request = buildModbusRequest();
    if (request.isEmpty())
        return;

    if (serialPort_->write(request) == -1) {
        QMessageBox::warning(this, "错误", "写入失败: " + serialPort_->errorString());
        return;
    }

    lastRequestFrame_ = request;
    currentRetryCount_ = 0;
    waitingResponse_ = true;
    startTimeoutMonitoring();
    setRequestButtonsEnabled(false);

    QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
    QString logEntry = QString("[%1] 写请求帧: %2").arg(timestamp, request.toHex(' ').toUpper());
    ui_->textEdit_log->append(logEntry);
    ui_->textEdit_log->moveCursor(QTextCursor::End);

    QMessageBox::information(this, "提示", "Modbus 帧已发送");
}

void MainWindow::onDeleteButtonClicked()
{
    ui_->textEdit_log->clear();
    QMessageBox::information(this, "提示", "日志已清空");
}

void MainWindow::onExportButtonClicked()
{
    QString logText = ui_->textEdit_log->toPlainText();
    if (logText.isEmpty()) {
        QMessageBox::information(this, "提示", "没有可导出的日志");
        return;
    }

    QString fileName = QFileDialog::getSaveFileName(this,
                                                    "导出日志",
                                                    QDir::homePath() + "/modbus_log.txt",
                                                    "文本文件 (*.txt)");
    if (fileName.isEmpty()) {
        QMessageBox::information(this, "提示", "导出已取消");
        return;
    }

    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QMessageBox::information(this, "提示", "无法打开文件保存日志");
        return;
    }

    QTextStream out(&file);
    out << logText;
    file.close();

    QMessageBox::information(this, "提示", "日志已成功导出到: " + fileName);
}

void MainWindow::parseModbusResponse(const QByteArray &response)
{
    if (response.size() < 5) {
        ui_->textEdit_log->append("接收到的响应帧太短，无法解析。");
        return;
    }

    QByteArray dataWithoutCrc = response.left(response.size() - 2);
    QByteArray receivedCrc = response.right(2);
    QByteArray calculatedCrc = calculateCrc(dataWithoutCrc);

    if (receivedCrc != calculatedCrc) {
        ui_->textEdit_log->append("响应帧 CRC 校验失败");
        return;
    }

    quint8 slaveAddr = static_cast<quint8>(response[0]);
    quint8 functionCode = static_cast<quint8>(response[1]);

    QString log;
    log += "响应帧: " + response.toHex(' ').toUpper() + "\n";
    log += QString("从站地址: 0x%1\n").arg(slaveAddr, 2, 16, QLatin1Char('0'));
    log += QString("功能码:    0x%1\n").arg(functionCode, 2, 16, QLatin1Char('0'));

    if (functionCode & 0x80) {
        quint8 exceptionCode = static_cast<quint8>(response[2]);
        QString exceptionMsg;
        switch (exceptionCode) {
        case 0x01: exceptionMsg = "非法功能码"; break;
        case 0x02: exceptionMsg = "非法数据地址"; break;
        case 0x03: exceptionMsg = "非法数据值"; break;
        case 0x04: exceptionMsg = "从站设备故障"; break;
        default:   exceptionMsg = "未知异常"; break;
        }
        log += QString("异常响应:异常码 0x%1 - %2\n").arg(exceptionCode, 2, 16, QLatin1Char('0')).arg(exceptionMsg);
        ui_->textEdit_log->append(log);
        return;
    }

    switch (functionCode) {
    case 0x01: // 读线圈
    {
    quint8 byteCount = static_cast<quint8>(response[2]);
    log += QString("字节数:   %1\n").arg(byteCount);

    int coilIndex = 0; // 当前线圈编号
    for (int i = 0; i < byteCount && coilIndex < expectedCoilCount; ++i) {
        quint8 byteVal = static_cast<quint8>(response[3 + i]);
        for (int b = 0; b < 8 && coilIndex < expectedCoilCount; ++b) {
            int bitVal = (byteVal >> b) & 0x01;
            log += QString("线圈[%1]:  %2\n").arg(coilIndex).arg(bitVal);
            ++coilIndex;
        }
    }
    break;
    }
    case 0x03:
    {
        if (response.size() < 3) return;
        quint8 byteCount = static_cast<quint8>(response[2]);
        log += QString("字节数:   %1\n").arg(byteCount);
        int coilIndex = 0;
        for (int i = 0; i < byteCount && coilIndex < expectedCoilCount_; ++i) {
            quint8 byteVal = static_cast<quint8>(response[3 + i]);
            for (int b = 0; b < 8 && coilIndex < expectedCoilCount_; ++b) {
                int bitVal = (byteVal >> b) & 0x01;
                log += QString("线圈[%1]:  %2\n").arg(coilIndex).arg(bitVal);
                ++coilIndex;
            }
        }
        break;
    }
    case 0x0F:
    case 0x10: {
        if (response.size() < 6) {
            log += "响应数据不足，无法解析写入结果。\n";
            break;
        }
        quint16 startAddr = static_cast<quint8>(response[2]) << 8 | static_cast<quint8>(response[3]);
        quint16 quantity = static_cast<quint8>(response[4]) << 8 | static_cast<quint8>(response[5]);
        log += QString("写入起始地址: %1\n").arg(startAddr);
        log += QString("写入数量: %1\n").arg(quantity);
        break;
    }
    default:
        log += QString("未识别的功能码: 0x%1\n").arg(functionCode, 2, 16, QLatin1Char('0')).toUpper();
        break;
    }

    log += "CRC校验通过\n";
    ui_->textEdit_log->append(log);
}

void MainWindow::onSerialDataReady()
{
    if (!waitingResponse_) return;

    responseBuffer_ += serialPort_->readAll();
    if (responseBuffer_.size() < 5) return;

    quint8 funcCode = static_cast<quint8>(responseBuffer_[1]);
    int expectedLen = 0;

    if (funcCode == 0x01 || funcCode == 0x03) {
        if (responseBuffer_.size() < 3) return;
        quint8 byteCount = static_cast<quint8>(responseBuffer_[2]);
        expectedLen = 3 + byteCount + 2;
    } else if (funcCode == 0x0F || funcCode == 0x10) {
        expectedLen = 8;
    } else if (funcCode & 0x80) {
        expectedLen = 5;
    } else {
        return;
    }

    if (responseBuffer_.size() < expectedLen) return;

    timeoutTimer_->stop();
    waitingResponse_ = false;
    currentRetryCount_ = 0;

    QByteArray fullResponse = responseBuffer_.left(expectedLen);
    responseBuffer_.remove(0, expectedLen);

    QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
    ui_->textEdit_log->append(QString("[%1] 响应帧解析").arg(timestamp));
    ui_->textEdit_log->moveCursor(QTextCursor::End);

    parseModbusResponse(fullResponse);
    setRequestButtonsEnabled(true);
}

void MainWindow::handleSerialError(QSerialPort::SerialPortError error)
{
    if (error == QSerialPort::ResourceError) {
        QMessageBox::critical(this, "错误", "串口连接丢失！");
        serialPort_->close();
        isConnected_ = false;
        ui_->connectButton->setText("连接");
        ui_->connectStatus->setText("未连接");
        ui_->logo->setIcon(QIcon(":/resource/image/noconnect.png"));
    }
}

void MainWindow::startTimeoutMonitoring()
{
    int timeoutMs = ui_->spinBox_timeout->value();
    timeoutTimer_->start(timeoutMs);
}

void MainWindow::handleTimeout()
{
    if (!waitingResponse_) return;

    timeoutTimer_->stop();
    int maxRetries = ui_->spinBox_num->value();

    if (currentRetryCount_ >= maxRetries) {
        QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
        ui_->textEdit_log->append(QString("[%1] 超过最大重发次数，终止请求\n").arg(timestamp));
        ui_->textEdit_log->moveCursor(QTextCursor::End);
        waitingResponse_ = false;
        setRequestButtonsEnabled(true);
        return;
    }

    ++currentRetryCount_;
    serialPort_->write(lastRequestFrame_);
    timeoutTimer_->start(ui_->spinBox_timeout->value());

    QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
    QString logEntry = QString("[%1] 超时未响应，准备重发 第 %2 次").arg(timestamp).arg(currentRetryCount_);
    ui_->textEdit_log->append(logEntry);
    ui_->textEdit_log->moveCursor(QTextCursor::End);
}
